#include "cjson.h"
#include "cJSON.h"

static void _cjson_add_node_content_by_type(cJSON *json_hd, enum cjson_format json_fmt, void *which_node, void *data)
{
    cJSON *find_item = NULL;

    find_item = cJSON_GetObjectItem(json_hd, which_node);
    if (find_item)
        return;

    /* find type for not being found */
    switch (json_fmt) {
        case CJSON_FORMAT_NUMBER_UNSIGNED_CHAR:
            cJSON_AddNumberToObject(json_hd, which_node, *(unsigned char*)data);
        break;
        case CJSON_FORMAT_NUMBER_INT:
            cJSON_AddNumberToObject(json_hd, which_node, *(int*)data);
        break;
        case CJSON_FORMAT_NUMBER_DOUBLE:
            cJSON_AddNumberToObject(json_hd, which_node, *(double*)data);
        break;
        case CJSON_FORMAT_NUMBER_LONG:
            cJSON_AddNumberToObject(json_hd, which_node, *(long*)data);
        break;
        case CJSON_FORMAT_STRING:
            cJSON_AddStringToObject(json_hd, which_node, (char *)data);
        break;
        default : break;
    }
}

static int _cjson_update_json_node_content_by_type(cJSON *json_hd, void *which_node, enum cjson_format json_fmt, void *data)
{
    cJSON *find_item = NULL;

    find_item = cJSON_GetObjectItem(json_hd, which_node);
    if (!find_item)
        return -1;

    /* find type for not being found */
    switch (json_fmt) {
        case CJSON_FORMAT_STRING:
            cJSON_ReplaceItemInObject(json_hd, which_node, cJSON_CreateString(data));
        break;

        case CJSON_FORMAT_NUMBER_UNSIGNED_CHAR:
            cJSON_ReplaceItemInObject(json_hd, which_node, cJSON_CreateNumber(*(unsigned char *)data));
        break;

        case CJSON_FORMAT_NUMBER_INT:
            cJSON_ReplaceItemInObject(json_hd, which_node, cJSON_CreateNumber(*(int*)data));
        break;

        case CJSON_FORMAT_NUMBER_DOUBLE:
            cJSON_ReplaceItemInObject(json_hd, which_node, cJSON_CreateNumber(*(double*)data));
        break;

        case CJSON_FORMAT_NUMBER_LONG:
            cJSON_ReplaceItemInObject(json_hd, which_node, cJSON_CreateNumber(*(long*)data));
        break;

        default: return -1;
    }
    return 0;
}

static cJSON *_cjson_get_node_content(cJSON *json_hd, void *which_node)
{
    return cJSON_GetObjectItem(json_hd, which_node);
}

static void _cjson_delete_node_content(cJSON *json_hd, void *node)
{
    cJSON_DeleteItemFromObject(json_hd, (const char *)node);
}

static int __json_get_file_buffer(const char *filename, char *buffer)
{
    int res;
    FILE *file = NULL;

    file = fopen(filename, "a+");
    if (!file) {
        perror("fopen file error");
        return -1;
    }

    res = fread(buffer, sizeof(unsigned char), CJSON_BUFFER_SIZE, file);
    if (-1 == res) {
        perror("fread buffer error");
        goto out;
    }

out:
    fclose(file);
    return res;
}

static cJSON *_cjson_create_json_obj(const char *file)
{
    int res;
    char buffer[CJSON_BUFFER_SIZE] = "";

    res = __json_get_file_buffer(file, buffer);
    if (-1 == res) {
        perror("__json get file buffer error");
        goto out;
    }

    return cJSON_Parse(buffer);

out:
    return cJSON_CreateObject();
}

static int __cjson_set_file_buffer(const char *filename, char *buffer, int len)
{
    FILE *file = NULL;
    int res;

    file = fopen(filename, "wb");
    if (!file) {
        perror("open file error");
        return -1;
    }

    res = fwrite(buffer, sizeof(unsigned char), len, file);
    if (-1 == res) {
        perror("fwrite file error");
        goto out;
    }

out:
    fclose(file);
    return res;
}

static void __cjson_init(const char *file)
{
    char *data = NULL;
    int res;

    data = "{ \n }";

    res = __cjson_set_file_buffer(file, data, strlen(data));
    if (-1 == res) {
        perror("__json set file buffer error");
        return;
    }
}

static void _cjson_init(const char *file)
{
    return __cjson_init(file);
}

static void _cjson_update_json_obj(const char *file, cJSON *json)
{
    char *data = NULL;
    int res;

    data = cJSON_Print(json);
    if (!data) {
        __cjson_init(file);
        return;
    }

    res = __cjson_set_file_buffer(file, data, strlen(data));
    if (-1 == res) {
        perror("__json set file buffer error");
        return;
    }
    return;
}

static void _cjson_delete_json_obj(cJSON *obj)
{
    cJSON_Delete(obj);
}

static void __cjson_get_fmt(cJSON *item, enum cjson_format cjson_fmt, void *out)
{
    double number = cJSON_GetNumberValue(item);

    /* only format for number */
    switch (cjson_fmt) {
        case CJSON_FORMAT_NUMBER_DOUBLE:
        {
            memcpy(out, &number, sizeof(double));
            break;
        }
        case CJSON_FORMAT_NUMBER_LONG:
        {
            memcpy(out, &number, sizeof(long));
            break;
        }
        case CJSON_FORMAT_NUMBER_INT:
        {
            memcpy(out, &number, sizeof(int));
            break;
        }
        case CJSON_FORMAT_NUMBER_UNSIGNED_CHAR:
        {
            memcpy(out, &number, sizeof(unsigned char));
            break;
        }
        default:break;
    }
}

static void _cjson_differ_format_by_one_leve(cJSON *item, enum cjson_format cjson_fmt, void *out)
{
    if (cJSON_IsString(item) && CJSON_FORMAT_STRING == cjson_fmt) {
        memcpy(out, item->valuestring, strlen(item->valuestring) + 1);
    }
    else if (cJSON_IsNumber(item)) {
        __cjson_get_fmt(item, cjson_fmt, out);
    } if (cJSON_IsNull(item)) {
        return;
    }
    return;
}

static void __cjson_differ_format_by_two_leve_by_number_format(cJSON *item, enum cjson_format fmt, void *out)
{
    return __cjson_get_fmt(item, fmt, out);
}

static void __cjson_differ_format_by_two_leve_by_string_format(cJSON *item, enum cjson_format fmt, void *out)
{
    if (CJSON_FORMAT_STRING != fmt)
        return;

    memcpy(out, item->valuestring, strlen(item->valuestring) + 1);
}

static void _cjson_differ_format_by_two_leve(cJSON *item, void *key, enum cjson_format fmt, void *data)
{
    if (!item)
        return;
        
    if (strcmp(item->string, (char *)key))
        return;

    if (cJSON_IsString(item)) {
        __cjson_differ_format_by_two_leve_by_string_format(item, fmt, data);
    } else if (cJSON_IsNumber(item)) {
        __cjson_differ_format_by_two_leve_by_number_format(item, fmt, data);
    }
    return;
}

int cjson_set_data_by_one_level(const char *file, void *which_node, enum cjson_format cjson_fmt, void *data)
{
    cJSON *obj = NULL;
    cJSON *item = NULL;
    int res;

    obj =_cjson_create_json_obj(file);
    if (!obj) {
        perror("_json create json obj error");
        return -1;
    }

    item = _cjson_get_node_content(obj, which_node);
    if (!item) {
        _cjson_add_node_content_by_type(obj, cjson_fmt, which_node, data);
    } else { 
        _cjson_delete_node_content(obj, item);
        res = _cjson_update_json_node_content_by_type(obj, which_node, cjson_fmt, data);
        if (-1 == res)
            goto out;
    }
    _cjson_update_json_obj(file, obj);
    _cjson_delete_json_obj(obj);
    return 0;
out:
    _cjson_delete_json_obj(obj);
    return -1;
}

int cjson_get_data_by_one_level(const char *file, void *which_node, enum cjson_format cjson_fmt, void *info)
{
    cJSON *obj = NULL;
    cJSON *item = NULL;
    
    obj =_cjson_create_json_obj(file);
    if (!obj) {
        perror("_json create json obj error");
        return -1;
    }

    item = _cjson_get_node_content(obj, which_node);
    if (!item)
        goto out;

    _cjson_differ_format_by_one_leve(item, cjson_fmt, info);
    _cjson_delete_node_content(obj, which_node);

    return 0;

out:
    _cjson_delete_json_obj(obj);
    return -1;
}

int cjson_delete_data_by_one_level(const char *file, void *which_node)
{
    cJSON *obj = NULL;
    cJSON *item = NULL;
    
    obj =_cjson_create_json_obj(file);
    if (!obj) {
        perror("_json create json obj error");
        return -1;
    }

    item = _cjson_get_node_content(obj, which_node);
    if (!item) {
        goto out;
    } else {
        _cjson_delete_node_content(obj, which_node);
    }
    
    _cjson_update_json_obj(file, obj);
    _cjson_delete_json_obj(obj);
    return 0;

out:
    _cjson_delete_json_obj(obj);
    return -1;
}

int cjson_init_file(const char *file) 
{
    _cjson_init(file);
    return 0;
}

int cjson_set_data_by_two_level(const char *file, void *first_node, void *second_node, enum cjson_format fmt, void *data)
{
    cJSON *root = NULL;
    cJSON *sub = NULL;
    cJSON *item = NULL;
    cJSON *new_item = NULL;
    int res;

    root = _cjson_create_json_obj(file);

    sub = cJSON_GetObjectItemCaseSensitive(root, first_node);
    if (!sub) {
        new_item = cJSON_CreateObject();
        _cjson_add_node_content_by_type(new_item, fmt, second_node, data);
        cJSON_AddItemToObject(root, first_node, new_item);
       goto init;
    }

    /* if the content of specific node is not empty, then it would be added by type */

    item = _cjson_get_node_content(sub, first_node);
    if (item) {
        _cjson_add_node_content_by_type(sub, fmt, second_node, data);
    } else {
        _cjson_delete_node_content(sub, item);
        res = _cjson_update_json_node_content_by_type(sub, second_node, fmt, data);
        if (-1 == res)
            goto out;
    }

    _cjson_update_json_obj(file, root);
    _cjson_delete_json_obj(root);
    return 0;
out:
    _cjson_delete_json_obj(root);
    return -1;
init:
    _cjson_update_json_obj(file, root);
    _cjson_delete_json_obj(root);
    return 0;
}

int cjson_get_data_by_two_level(const char *file, void *first_node, void *second_node, enum cjson_format fmt, void *data)
{
    cJSON *obj = NULL;
    cJSON *sub = NULL;
    int j = 0;
    cJSON *item_e = NULL;
    
    obj = _cjson_create_json_obj(file);
    if (!obj) {
        perror("get obj error");
        return -1;
    }

    /* find sub node */
    sub = cJSON_GetObjectItemCaseSensitive(obj, first_node);
    if (!cJSON_IsObject(sub)) {
        perror("get item sensitive error");
        goto out;
    }

    for (; j < cJSON_GetArraySize(sub); j++) {
        item_e = cJSON_GetArrayItem(sub, j);
        _cjson_differ_format_by_two_leve(item_e, second_node, fmt, data);
    }

    _cjson_delete_json_obj(obj);
    return 0;

out:
    _cjson_delete_json_obj(obj);
    return -1;
}

int cjson_delete_data_by_two_level(const char *file, void *first_node, void *second_node)
{
    cJSON *obj = NULL;
    cJSON *sub = NULL;
    cJSON *item = NULL;
    
    obj =_cjson_create_json_obj(file);
    if (!obj) {
        perror("_json create json obj error");
        return -1;
    }

    /* find sub */
    sub = cJSON_GetObjectItemCaseSensitive(obj, first_node);
    if (!sub) {
        goto out;
    }

    item = _cjson_get_node_content(sub, second_node);
    if (!item) {
        return -1;
    } else {
        _cjson_delete_node_content(sub, second_node);
    }
    
    _cjson_update_json_obj(file, obj);
    _cjson_delete_json_obj(obj);
    return 0;
out:
    _cjson_delete_json_obj(obj);
    return -1;
}